#!/bin/bash

#- DOCS
#-- NOTES
# - (08d-03m-21y) lkp NEEDS to run as root
#
#-- FUNCTION DOCUMENTATION
# - INPUT: 	a list of parameters to pass
#		[ext] list of extern variable used
# - SET: 	variables that are initializated or left untouched otherwise
# - MODIFY:	variables that are modified
#
#---

#- CODE
#-- IMPORT
# shellcheck disable=SC1091
. ../../lib/sh-test-lib
#-- VARIABLES
# It's used by lkp to correctly set PKGBUILD of some programs. Declation and
# assignment are separated because of shellcheck SC2155 "masking return value"
export arch
arch="$(uname -m)"

TEST_PROGRAM="lkp-tests"
TEST_DIR="$(pwd)/${TEST_PROGRAM}"
TEST_PROG_VERSION="HEAD"
OUTPUT_DIR="$(pwd)/output"
RESULT_FILE="${OUTPUT_DIR}/result.txt"

TEST_GIT_URL="https://github.com/intel/lkp-tests.git"
LKP_PATH=''

export RESULT_FILE
TEST_QUEUE=''	# tests to run (set by args)
ATOMIC_JOBS=''	# single jobs to run
ATOMIC_FILES=''	# list of atomic-jobs generated by a test
QUIET=''	# append to command to manage verbosity
#--- flags with default values
SKIP_INSTALL='false'
SKIP_TEST_INSTALL='false'
#--FUNCTIONS DEFINITIONS
#--- HELP TEXT
usage()
{
	printf "%s\n" "
	lkp.sh [-h] [-s true|false] -t \"<test> [test ...]\" [-T \"atomic-job.yaml ... ]
	NOTE: do not compose short-option in single string (eg: '-st' is incorrect)

	OPTION:
	-h, --help		print this help
	-d, --install-dir	where to clone and run the LKP suite
	-g, --git-repo		use the git repository passed. if! the standard one used
	-q, --quiet		suppress lkp output and leave only the script one
	-s, --skip-install	skip the installation of lkp (not single test ones)
	-S, --skip-test-install	skip the installation of test dependencies
	-t, --test		which tests will be executed
	-T, --atomic-job	which atomic-jobs will be executed (empty for all)
	-V, --prog-version	git commit to use in repository

	NOTE:
	- use quotes and usa space-char to separate tests and atomic-job names
	  (eg: -t \"test0 test1 test2\")
	- test name must be present inside [...]lkp/jobs/<test-name>.yaml
	- atomic-job name must be exactly one generated by \"lkp split-job\" with '.yaml' ext

	INSTALLATION:
	-s|--skip-install decides if the script has to run installation procedure.
	At first LKP is searched: if present, only dependecies are checked;
	otherwise, lkp is installed from git-repo with the full procedure.
	-S|--skip-test-install decides if 'lkp install <atomic-job>' is to be run.
	ATTENTION unmet test dependencies could lead to test errors which are not
	captured [see OUTPUT in docs]

	OUTPUT
	LKP doesn't report the single atomic-jobs ret-value, but it has to be
	gathered inside test output; since every test has a different format,
	a specific output-parsing function is needed.
	Reading lkp ret-val gives 1 ONLY if LKP command gets an error, so 0 is
	returned if atomic-job fails but lkp program itself doesn't.
	Main tests are reported 'pass' when all child atomic-jobs are run
	despite their individual exit status.
	'report_atomic_job()' function is used and should be modified to parse
	atomic-job output and tell the script what has to be reported.
	'skip' is reported for tests that this script doesn't know about, even if
	they are executed correctly.

	SECURITY CONCERN
	The fastest and shortest way to use QUIET variable for output suppression,
	is to use 'eval <command> \$QUIET, otherwise the variable content as
	parameters and not shell directive. This cause two potentialy security flaws,
	since there is no input sanitisation of -t and -T flags.
	"

	exit 1
}

#--- ARGS PARSING
# - INPUT: 	$1 Use "$@" for parsing (use double-quotes to avoid error with char)
# - SET:	#
# - MODIFY: 	#
argument_parsing()
{
	while [ "$#" -gt 0 ]; do
			param=$1
			case ${param} in

			-d | --install-dir)
				if [[ "${2}" != "" ]]; then
					TEST_DIR="${2}/${TEST_PROGRAM}"
				fi
				shift; shift;
				;;
			-g | --git-repo)
				TEST_GIT_URL="$2"
				shift; shift;
				;;
			-q | --quiet)
				QUIET='>/dev/null 2>&1'
				shift;
				;;
			-s | --skip-install)
				#${var,,} make variable to lowercase; require:bash >=4.0
				if [ "${2,,}" = 'true' ] || [ "${2,,}" = 'false' ] ; then
					SKIP_INSTALL="${2,,}"
					shift; shift;
				else
					error_msg "'--skip-install' value must be [true|false]"
					exit 1
				fi
				;;
			-S | --skip-test-install)
				#${var,,} make variable to lowercase; require:bash >=4.0
				if [ "${2,,}" = 'true' ] || [ "${2,,}" = 'false' ] ; then
					SKIP_TEST_INSTALL="${2,,}"
					shift; shift;
				else
					error_msg "'--skip-test-install' value must be [true|false]"
					exit 1
				fi
				;;
			-t | --test)
				read -r -a TEST_QUEUE <<< "$2"
				shift; shift;
				;;

			-T | --atomic-job)
				read -r -a ATOMIC_JOBS <<< "$2"
				shift; shift;
				;;

			-V | --prog-version)
				if [[ "${2}" != '' ]]; then
					TEST_PROG_VERSION="${2}"
				fi
				shift; shift;
				;;

			-h | --help)
				usage
				exit 1
				;;
			*)
				error_msg "'${param}' is not a defined option"
				exit 1;
				;;
			esac
	done
}

#--- Install lkp from git repository unless otherwise specified
# - INPUT: 	<None>
# - SET:	LKP_PATH
# - MODIFY: 	PWD
install()
{
	if [[ "${SKIP_INSTALL}" = "false" ]]; then
		info_msg "LKP installation ..."
		#avoid lkp installation to prompt user creation
		useradd lkp -m -p lkp || [[ "$?" == "9" ]]
		[[ -n "${TEST_GIT_URL}" ]] && pkgs+=" git"
		pkgs+=" make"
		pkgs+=" build-essential" #avoid aarch64 incorrect lkp installation
		pkgs+=" jq"	#parse json result file
		install_deps "${pkgs}"
		get_test_program "${TEST_GIT_URL}" "${TEST_DIR}" "${TEST_PROG_VERSION}" "${TEST_PROGRAM}"

		cd "${TEST_DIR}" || exit 1

		eval make install "${QUIET}"
		eval lkp install "${QUIET}"
	else
		info_msg "LKP installation skipped"
	fi

	LKP_PATH=$(readlink -f "$(which lkp)" 2>/dev/null | sed 's:bin.*::')
}

#--- Install single atomic-jobs dependencies unless otherwise specified
# - INPUT: 	[ext] ATOMIC_JOBS[@]
# - SET:	<None>
# - MODIFY: 	<None>
install_atomic_jobs_dependencies()
{
	if [[ "${SKIP_TEST_INSTALL}" = "false" ]]; then
		info_msg "--Installing atomic jobs..."
		for job in "${ATOMIC_JOBS[@]}"; do
			echo "lkp install ${job} ${QUIET}"
			lkp install "${job}" "${QUIET}"
		done
	else
		info_msg "--Skip atomic jobs dependencies installation"
	fi
}
#--- Create atomic-jobs from test with $ lkp split-job
# - INPUT: 	[ext] test
# - SET: 	ATOMIC_JOBS
# - MODIFY	<None>
create_atomic_jobs()
{
	info_msg "--Creating atomic-jobs for ${test}..."
	readarray -t ATOMIC_FILES <<< "$(lkp split-job "${LKP_PATH}"/jobs/"${test}".yaml | cut -f 3 -d ' ')"

}

#--- Get atomic job exit code from result files
# - INPUT: 	$1 =  Name of .yaml file run by run_atomic_job()
# - SET:	<None>
# - MODIFY: 	<None>
report_atomic_job()
{
	info_msg "++Getting result of ${job}..."

	#chekc if output has been generated
	if [[ ! -e "${TEST_DIR}/working/result" ]] ; then
		info_msg "${1} didn't produce any results."
		report_fail "${1}"
		return
	fi

	local FILE="${TEST_DIR}/working/result/${test}.json"
	local FIELD
	local VALUE
	#check main result output file
	if [[ ! -e "${FILE}" ]]; then
		info_msg "${FILE} doesn't exists"
		report_skip "${1}"
	else
		info_msg "Parsing ${FILE}..."
		while IFS= read -r line; do
			FIELD=$(echo "${line}" | cut -f 1 -d ' ' | sed "s:${test}\.::")
			VALUE=$(echo "${line}" | cut -f 2 -d ' ')
			add_metric "${1}" "pass" "${VALUE}" "${FIELD}"
		done < <(jq -r 'to_entries[] | "\(.key) \(.value)"' <"${FILE}" | sed 's:[][]::g')
		report_pass "${1}"
	fi

	# remove link to avoid different test-results access
	rm  -f "${TEST_DIR}/working/result"
}


#--- Run an atomic job created by $lkp split-job
# - INPUT: 	$1 =  Name of .yaml file to run
# - SET:	<None>
# - MODIFY: 	<None>
run_atomic_job()
{
	[ "$#" -ne 1 ] && error_msg "Usage: run_atomic_job"

	info_msg "++Running ${job}..."
	eval lkp run ./"${1}" "${QUIET}"
	report_atomic_job "${1}"

}
#------------------------------------------------------------------------------#
#--- MAIN ---------------------------------------------------------------------#

! check_root && error_msg "This script must be run as root"

argument_parsing "$@"

if [[ -z "${TEST_QUEUE[*]}" ]]; then
	error_msg "No test has been passed to the script"
	exit 1
fi

#--- changing the order can break installation process
install
create_out_dir "${OUTPUT_DIR}"
report_set_start "LKP"
#---

info_msg "Running..."
# 'working' is for atomic-jobs .yaml file
mkdir -p "${TEST_DIR}/working" && cd "${TEST_DIR}/working" || exit 1

# Running different tests passed with -t flag
for test in "${TEST_QUEUE[@]}"; do
	info_msg "+Running: ${test}"

	create_atomic_jobs

	#Atomic Jobs specified with -T flag
	if [[ -z "${ATOMIC_JOBS[*]}" ]]; then
		info_msg "No atomic-job specified; running all"
		ATOMIC_JOBS=("${ATOMIC_FILES[@]}")
	fi

	if [ "${SKIP_TEST_INSTALL}" = 'false' ]; then
		install_atomic_jobs_dependencies
	fi

	for job in "${ATOMIC_JOBS[@]}"; do
		if [[ -z "${ATOMIC_JOBS[*]}" ]] || [[ "${ATOMIC_JOBS[*]}" =~ ${job:2} ]]; then
			run_atomic_job "${job}"
		else
			info_msg "++Ignoring ${job}"
		fi
	done

	cp -r "/lkp/result/${test}" "${OUTPUT_DIR}" || info_msg "Impossible to copy results of ${test}"
	report_pass "${test}"
done

report_set_stop

#--- END OF MAIN --------------------------------------------------------------#
#------------------------------------------------------------------------------#
